/* Copyright (c) 2008-2010, developers of the Ascension Log Visualizer
 *
 * Permission is hereby granted, free of charge, to any person
 * obtaining a copy of this software and associated documentation
 * files (the "Software"), to deal in the Software without
 * restriction, including without limitation the rights to use,
 * copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom
 * the Software is furnished to do so, subject to the following
 * conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 * OTHER DEALINGS IN THE SOFTWARE.
 */

package com.googlecode.logVisualizer.gui.notetaker;

import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import java.io.IOException;
import java.util.Iterator;

import javax.swing.*;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;

import org.jfree.ui.RefineryUtilities;

import com.googlecode.logVisualizer.Settings;
import com.googlecode.logVisualizer.logData.LogDataHolder;
import com.googlecode.logVisualizer.logData.turn.TurnInterval;
import com.googlecode.logVisualizer.logData.turn.turnAction.DayChange;
import com.googlecode.logVisualizer.parser.LogsCreator;
import com.googlecode.logVisualizer.util.LookAheadIterator;
import com.googlecode.logVisualizer.util.textualLogs.TextLogCreator;
import com.googlecode.logVisualizer.util.textualLogs.TextLogCreator.TextualLogVersion;

/**
 * This class is ascension log notes editor, that gives the user a basic
 * interface to manage log notes.
 */
public final class Notetaker extends JFrame {
    private final LogDataHolder log;

    private final JButton saveButton;

    private final TurnIntervalMenuList turnIntervalMenu;

    private final JTextArea notesArea;

    private TurnIntervalContainer activeTurnInterval;

    /**
     * Will show a dialog to let the user choose which turncounts should be
     * included inside the Notetaker and then show the actual Notetaker
     * interface based on that decision.
     */
    public static void showNotetaker(
                                     final LogDataHolder log) {
        final JComboBox selectionBox = new JComboBox();
        selectionBox.setPreferredSize(new Dimension(400, selectionBox.getPreferredSize().height));

        // Populate the selection combo box with all possible intervals.
        selectionBox.addItem(new LogInterval("Full log", -1, Integer.MAX_VALUE));
        final LookAheadIterator<DayChange> index = new LookAheadIterator<DayChange>(log.getDayChanges()
                                                                                       .iterator());
        while (index.hasNext()) {
            final DayChange dc = index.next();
            final int nextDayChange = index.peek() != null ? index.peek().getTurnNumber()
                                                          : Integer.MAX_VALUE;
            selectionBox.addItem(new LogInterval("Day " + dc.getDayNumber(),
                                                 dc.getTurnNumber(),
                                                 nextDayChange));
        }
        selectionBox.setSelectedIndex(0);

        // Show the interval selection dialog.
        JOptionPane.showMessageDialog(null,
                                      selectionBox,
                                      "Select what interval that should be displayed in the Notetaker",
                                      JOptionPane.QUESTION_MESSAGE);

        // Create and show the actual Notetaker interface with the chosen
        // interval.
        final LogInterval selection = (LogInterval) selectionBox.getSelectedItem();
        if (selection.getName().equals("Full log"))
            new Notetaker(log);
        else
            new Notetaker(log.getSubIntervalLogData(selection.getStartTurn(),
                                                    selection.getEndTurn()));
    }

    /**
     * Creates the notes editor frame.
     * 
     * @param log
     *            The log data whose notes should be managed.
     */
    Notetaker(
              final LogDataHolder log) {
        super("Notetaker for " + log.getLogName());
        setDefaultCloseOperation(DISPOSE_ON_CLOSE);
        setLayout(new BorderLayout(5, 25));

        this.log = log;
        saveButton = new JButton("Save log to file");
        turnIntervalMenu = new TurnIntervalMenuList(log);
        notesArea = new JTextArea();
        notesArea.setLineWrap(true);
        notesArea.setWrapStyleWord(true);
        getContentPane().add(createNotePanel(), BorderLayout.CENTER);
        getContentPane().add(createButtonPanel(), BorderLayout.SOUTH);
        addListeners();
        turnIntervalMenu.setSelectedIndex(0);

        setSize(800, 600);
        RefineryUtilities.centerFrameOnScreen(this);
        setVisible(true);
    }

    private JComponent createNotePanel() {
        final JSplitPane splitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT);

        splitPane.setTopComponent(new JScrollPane(turnIntervalMenu));
        splitPane.setBottomComponent(new JScrollPane(notesArea));
        splitPane.setDividerLocation(400);

        return splitPane;
    }

    private JPanel createButtonPanel() {
        final JPanel buttonPanel = new JPanel(new GridLayout(1, 0, 200, 0));
        final JButton closeButton = new JButton("Close window");

        saveButton.setPreferredSize(new Dimension(0, 40));
        closeButton.setPreferredSize(new Dimension(0, 40));
        closeButton.addActionListener(new ActionListener() {
            public void actionPerformed(
                                        final ActionEvent e) {
                if (activeTurnInterval != null)
                    activeTurnInterval.setNotes(notesArea.getText());

                dispose();
            }
        });

        buttonPanel.add(saveButton);
        buttonPanel.add(closeButton);

        return buttonPanel;
    }

    private void addListeners() {
        turnIntervalMenu.addListSelectionListener(new ListSelectionListener() {
            public void valueChanged(
                                     final ListSelectionEvent lse) {
                if (!lse.getValueIsAdjusting()) {
                    if (activeTurnInterval != null)
                        activeTurnInterval.setNotes(notesArea.getText());

                    activeTurnInterval = turnIntervalMenu.getCurrentlySelectedTurnInterval();
                    notesArea.setText(activeTurnInterval.getNotes());
                }
            }
        });
        saveButton.addActionListener(new ActionListener() {
            public void actionPerformed(
                                        final ActionEvent e) {
                if (activeTurnInterval != null)
                    activeTurnInterval.setNotes(notesArea.getText());

                new SaveDialog();
            }
        });
    }

    /**
     * ListCellRenderer for turn intervals, because the need to be able to show
     * multi-line strings as texts with multiple lines.
     */
    private static final class TurnIntervalCellRenderer extends JTextArea implements
            ListCellRenderer {

        TurnIntervalCellRenderer() {
            setLineWrap(true);
            setWrapStyleWord(true);
            setEditable(false);
            setOpaque(true);
        }

        public Component getListCellRendererComponent(
                                                      final JList list, final Object value,
                                                      final int index, final boolean isSelected,
                                                      final boolean cellHasFocus) {
            setText(value.toString());

            if (isSelected) {
                setBackground(list.getSelectionBackground());
                setForeground(list.getSelectionForeground());
            } else {
                setBackground(list.getBackground());
                setForeground(list.getForeground());
            }

            return this;
        }
    }

    /**
     * Just a little helper class make instantiation of the turn interval menu a
     * little nicer.
     */
    private static final class TurnIntervalMenuList extends JList {

        /**
         * Creates the turn interval menu.
         * 
         * @param log
         *            The log data whose notes should be managed.
         */
        TurnIntervalMenuList(
                             final LogDataHolder log) {
            super(new DefaultListModel());
            setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
            setCellRenderer(new TurnIntervalCellRenderer());

            final Iterator<String> turnRundownListIndex = TextLogCreator.getTurnRundownList(log)
                                                                        .iterator();
            for (final TurnInterval ti : log.getTurnsSpent())
                ((DefaultListModel) getModel()).addElement(new TurnIntervalContainer(ti,
                                                                                     turnRundownListIndex.next()));
        }

        TurnIntervalContainer getCurrentlySelectedTurnInterval() {
            if (isSelectionEmpty())
                throw new IllegalStateException("No turn interval is currently selected.");

            return (TurnIntervalContainer) ((DefaultListModel) getModel()).get(getSelectedIndex());
        }
    }

    /**
     * The dialog that is seen when one wants to save the log to a file.
     */
    private final class SaveDialog extends JDialog {
        private final JTextField directoryLocationField = new JTextField(Settings.getSettingString("Parsed logs saving location"));

        private TextualLogVersion logVersion = TextualLogVersion.TEXT_LOG;

        /**
         * Creates and shows the save dialog in the centre of the screen. Please
         * note that this dialog is also modal on the notes editor frame.
         */
        SaveDialog() {
            super(Notetaker.this, true);
            setTitle("Choose saving directory for the ascension log");
            setLayout(new BorderLayout(0, 10));

            getContentPane().add(createLogVersionChooserPanel(), BorderLayout.NORTH);
            getContentPane().add(createDirectoryFinderPanel(), BorderLayout.CENTER);
            getContentPane().add(createButtonPanel(), BorderLayout.SOUTH);

            pack();
            setSize(500, getSize().height);
            RefineryUtilities.centerFrameOnScreen(this);
            setVisible(true);
        }

        private JPanel createLogVersionChooserPanel() {
            final JPanel panel = new JPanel(new GridLayout(1, 3, 10, 10));
            final JRadioButton textButton = new JRadioButton("Text", true);
            final JRadioButton htmlButton = new JRadioButton("HTML", false);
            final JRadioButton bbcodeButton = new JRadioButton("BBCode", false);
            final ButtonGroup group = new ButtonGroup();
            group.add(textButton);
            group.add(htmlButton);
            group.add(bbcodeButton);

            final ChangeListener listener = new ChangeListener() {
                public void stateChanged(
                                         final ChangeEvent e) {
                    if (htmlButton.isSelected())
                        logVersion = TextualLogVersion.HTML_LOG;
                    else if (bbcodeButton.isSelected())
                        logVersion = TextualLogVersion.BBCODE_LOG;
                    else
                        logVersion = TextualLogVersion.TEXT_LOG;
                }
            };
            textButton.addChangeListener(listener);
            htmlButton.addChangeListener(listener);
            bbcodeButton.addChangeListener(listener);

            panel.add(textButton);
            panel.add(htmlButton);
            panel.add(bbcodeButton);

            return panel;
        }

        private JPanel createDirectoryFinderPanel() {
            final JPanel panel = new JPanel(new GridBagLayout());
            final JButton directoryChooserButton = new JButton("Find Directory");
            GridBagConstraints gbc;

            gbc = new GridBagConstraints();
            gbc.gridx = 1;
            gbc.gridy = 0;
            gbc.anchor = GridBagConstraints.WEST;
            gbc.fill = GridBagConstraints.HORIZONTAL;
            gbc.weightx = 1.0;
            gbc.weighty = 1.0;
            gbc.insets = new Insets(10, 10, 5, 0);
            panel.add(directoryLocationField, gbc);

            gbc = new GridBagConstraints();
            gbc.gridx = 2;
            gbc.gridy = 0;
            gbc.anchor = GridBagConstraints.EAST;
            gbc.insets = new Insets(10, 25, 5, 10);
            panel.add(directoryChooserButton, gbc);

            File logsDirectory = new File(directoryLocationField.getText());
            if (!logsDirectory.exists())
                logsDirectory = null;

            final JFileChooser directoryChooser = new JFileChooser(logsDirectory);
            directoryChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
            directoryChooserButton.addActionListener(new ActionListener() {
                public void actionPerformed(
                                            final ActionEvent e) {
                    final int state = directoryChooser.showOpenDialog(null);
                    if (state == JFileChooser.APPROVE_OPTION)
                        directoryLocationField.setText(directoryChooser.getSelectedFile()
                                                                       .getAbsolutePath());
                }
            });

            return panel;
        }

        private JPanel createButtonPanel() {
            final JPanel buttonPanel = new JPanel(new GridLayout(1, 0, 100, 0));
            final JButton closeButton = new JButton("Cancel");
            final JButton saveButton = new JButton("OK");

            saveButton.setPreferredSize(new Dimension(0, 30));
            saveButton.addActionListener(new ActionListener() {
                public void actionPerformed(
                                            final ActionEvent e) {
                    try {
                        String filePath = directoryLocationField.getText();
                        if (!filePath.endsWith(File.separator))
                            filePath += File.separator;

                        final File logsDest;
                        if (logVersion == TextualLogVersion.HTML_LOG)
                            logsDest = new File(filePath
                                                + LogsCreator.getParsedLogNameFromCondensedMafiaLog(log.getLogName())
                                                + ".html");
                        else
                            logsDest = new File(filePath
                                                + LogsCreator.getParsedLogNameFromCondensedMafiaLog(log.getLogName())
                                                + ".txt");
                        if (logsDest.exists())
                            logsDest.delete();
                        logsDest.createNewFile();

                        TextLogCreator.saveTextualLogToFile(log, logsDest, logVersion);

                        Settings.setSettingString("Parsed logs saving location",
                                                  directoryLocationField.getText());
                    } catch (final IOException e1) {
                        JOptionPane.showMessageDialog(null,
                                                      "A problem occurred while creating the log.",
                                                      "Error occurred",
                                                      JOptionPane.ERROR_MESSAGE);
                        e1.printStackTrace();
                    }
                    dispose();
                }
            });
            closeButton.setPreferredSize(new Dimension(0, 30));
            closeButton.addActionListener(new ActionListener() {
                public void actionPerformed(
                                            final ActionEvent e) {
                    dispose();
                }
            });

            buttonPanel.add(saveButton);
            buttonPanel.add(closeButton);

            return buttonPanel;
        }
    }

    private static final class LogInterval {
        private final String name;

        private final int startTurn;

        private final int endTurn;

        LogInterval(
                    final String name, final int startTurn, final int endTurn) {
            if (name == null)
                throw new IllegalArgumentException("The name must not be null.");

            this.name = name;
            this.startTurn = startTurn;
            this.endTurn = endTurn;
        }

        String getName() {
            return name;
        }

        int getStartTurn() {
            return startTurn;
        }

        int getEndTurn() {
            return endTurn;
        }

        @Override
        public String toString() {
            return name;
        }

        @Override
        public int hashCode() {
            final int prime = 31;
            int result = 8421;
            result = prime * result + startTurn;
            result = prime * result + endTurn;
            result = prime * result + name.hashCode();
            return result;
        }

        @Override
        public boolean equals(
                              final Object obj) {
            if (this == obj)
                return true;
            if (obj == null)
                return false;
            if (!(obj instanceof LogInterval))
                return false;
            final LogInterval other = (LogInterval) obj;
            if (startTurn != other.startTurn)
                return false;
            if (endTurn != other.endTurn)
                return false;
            if (name == null) {
                if (other.name != null)
                    return false;
            } else if (!name.equals(other.name))
                return false;

            return true;
        }
    }
}
